﻿using System;
using System.ComponentModel;

namespace Microsoft.Services.Clab.UI.Mvvmc
{
#if (!SILVERLIGHT)
    [Serializable]
#endif
    public abstract class ViewModelBase : IViewModel, IPropertyAccessor
    {
        public ViewModelBase()
        {
            this.PropertyCollection = new ViewModelPropertyCollection();
            this.Views = new ViewCollection(this);
            this.CacheMetadata(new ViewModelMetadata(this));
        }

        protected internal ViewModelPropertyCollection PropertyCollection { get; private set; }

        protected virtual void CacheMetadata(ViewModelMetadata metadata)
        {
        }

        protected virtual void OnInitialized()
        {
        }

        protected virtual IView CreateView()
        {
            if (this.InitParameters.Contains("__VIEW_INSTANCE__"))
            {
                return this.InitParameters["__VIEW_INSTANCE__"].GetValue<IView>();
            }
            else
            {
                throw new NotImplementedException();
            }
        }

        protected object InitParameter { get; private set; }

        protected KeyValueCollection InitParameters { get; private set; }

        #region IViewModel Members

        #region Views Property
        public ViewCollection Views
        {
            get { return this.views; }
            private set { if (this.views != value) { this.views = value; this.OnPropertyChangedInternal("Views"); } }
        }
#if (!SILVERLIGHT)
        [NonSerialized]
#endif
        private ViewCollection views;
        #endregion

        #region Controller Property
        public IController Controller
        {
            get { return this.controller; }
            set { if (this.controller != value) { this.controller = value; this.OnPropertyChangedInternal("Controller"); } }
        }
#if (!SILVERLIGHT)
        [NonSerialized]
#endif
        private IController controller;
        #endregion

        #region ParentViewModel Property
        public IViewModel ParentViewModel
        {
            get { return this.parentViewModel; }
            set { if (this.parentViewModel != value) { this.parentViewModel = value; this.OnPropertyChangedInternal("ParentViewModel"); } }
        }
#if (!SILVERLIGHT)
        [NonSerialized]
#endif
        private IViewModel parentViewModel;
        #endregion

        #region State Property
        public ViewModelState State
        {
            get { return this.viewModelState; }
            set { if (this.viewModelState != value) { this.viewModelState = value; this.OnPropertyChangedInternal("State"); } }
        }
        private ViewModelState viewModelState;
        #endregion

        public void Initialize(IController controller, IViewModel parentViewModel, object initParameters)
        {
            DispatcherProxy.Invoke(() =>
            {
                this.InitParameters = initParameters as KeyValueCollection;
                this.InitParameter = InitParameter;
                this.Controller = controller;
                this.ParentViewModel = parentViewModel;

                var view = this.CreateView();
                this.Views.AddMainView(view);
            });

            OnInitialized();
            this.State = ViewModelState.Initialized;
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }

        internal void OnPropertyChangedInternal(string propertyName)
        {
            this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region IPropertyAccessor Members

        public object GetValue(string name)
        {
            return this.GetValue<object>(name);
        }

        public void SetValue(string name, object value)
        {
            this.SetValue<object>(name, value);
        }

        public bool PropertyExists(string name)
        {
            return this.PropertyCollection.Contains(name);
        }

        #endregion

        protected virtual T GetValue<T>(string name)
        {
            return this.PropertyCollection[name].GetValue<T>();
        }

        protected virtual void SetValue<T>(string name, T value)
        {
            this.PropertyCollection[name].SetValue(value);
        }

        protected virtual T GetInitParameter<T>(string name)
        {
            var item = this.Controller.InitParameters[name];
            if (item != null)
            {
                return item.GetValue<T>();
            }
            else
            {
                return default(T);
            }
        }

        protected static T FindParentViewModel<T>(IViewModel viewModel)
        {
            if (viewModel == null)
            {
                throw new ArgumentNullException("viewModel");
            }

            var parentVM = viewModel.ParentViewModel;
            while (parentVM != null)
            {
                if (parentVM is T)
                {
                    return (T)parentVM;
                }
                parentVM = parentVM.ParentViewModel;
            }
            return default(T);
        }

        protected void CreateSecondaryView<TView>(Action<IView> onViewInitialized) where TView : IView
        {
            DispatcherProxy.BeginInvoke(() =>
            {
                var view = Activator.CreateInstance<TView>();
                this.Views.Add(view);
                if (onViewInitialized != null)
                {
                    onViewInitialized(view);
                }
            });
        }
    }
}